import pandas as pd
import plotly.express as px
import numpy as np
import sklearn
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.colors import LogNorm
import warnings
warnings.filterwarnings('ignore')
np.random.seed(23)
from sklearn.model_selection import train_test_split
census_df = pd.read_csv('census_income_dataset.csv')
census_df.head(10)
| age | workclass | fnlwgt | education | education_num | marital_status | occupation | relationship | race | sex | capital_gain | capital_loss | hours_per_week | native_country | income_level | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 39 | State-gov | 77516.0 | Bachelors | 13 | Never-married | Adm-clerical | Not-in-family | White | Male | 2174.0 | 0.0 | 40.0 | United-States | <=50K |
| 1 | 50 | Self-emp-not-inc | 83311.0 | Bachelors | 13 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 0.0 | 0.0 | 13.0 | United-States | <=50K |
| 2 | 38 | Private | 215646.0 | HS-grad | 9 | Divorced | Handlers-cleaners | Not-in-family | White | Male | 0.0 | 0.0 | 40.0 | United-States | <=50K |
| 3 | 53 | Private | 234721.0 | 11th | 7 | Married-civ-spouse | Handlers-cleaners | Husband | Black | Male | 0.0 | 0.0 | 40.0 | United-States | <=50K |
| 4 | 28 | Private | 338409.0 | Bachelors | 13 | Married-civ-spouse | Prof-specialty | Wife | Black | Female | 0.0 | 0.0 | 40.0 | Cuba | <=50K |
| 5 | 37 | Private | 284582.0 | Masters | 14 | Married-civ-spouse | Exec-managerial | Wife | White | Female | 0.0 | 0.0 | 40.0 | United-States | <=50K |
| 6 | 49 | Private | 160187.0 | 9th | 5 | Married-spouse-absent | Other-service | Not-in-family | Black | Female | 0.0 | 0.0 | 16.0 | Jamaica | <=50K |
| 7 | 52 | Self-emp-not-inc | 209642.0 | HS-grad | 9 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 0.0 | 0.0 | 45.0 | United-States | >50K |
| 8 | 31 | Private | 45781.0 | Masters | 14 | Never-married | Prof-specialty | Not-in-family | White | Female | 14084.0 | 0.0 | 50.0 | United-States | >50K |
| 9 | 42 | Private | 159449.0 | Bachelors | 13 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 5178.0 | 0.0 | 40.0 | United-States | >50K |
pd.set_option('display.max_colwidth', -1)
descr = pd.read_csv('attributes_census_income.csv')
descr
| name | type | description | |
|---|---|---|---|
| 0 | age | integer | age of individual |
| 1 | workclass | string | Values: Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked |
| 2 | fnlwgt | float | Final sampling weight. Inverse of sampling fraction adjusted for non-response and over or under sampling of particular groups |
| 3 | education | string | Values: Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool |
| 4 | education_num | integer | NaN |
| 5 | marital_status | string | Values: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse |
| 6 | occupation | string | Values: Tech-support, Craft-repair, Other-service, Sales, Exec-managerial, Prof-specialty, Handlers-cleaners, Machine-op-inspct, Adm-clerical, Farming-fishing, Transport-moving, Priv-house-serv, Protective-serv, Armed-Forces |
| 7 | relationship | string | Values: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried |
| 8 | race | string | Values: White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other, Black |
| 9 | sex | string | Values: Female, Male |
| 10 | capital_gain | float | NaN |
| 11 | capital_loss | float | NaN |
| 12 | hours_per_week | float | working hours per week |
| 13 | native_country | string | Values: United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, Greece, South, China, Cuba, Iran, Honduras, Philippines, Italy, Poland, Jamaica, Vietnam, Mexico, Portugal, Ireland, France, Dominican-Republic, Laos, Ecuador, Taiwan, Haiti, Columbia, Hungary, Guatemala, Nicaragua, Scotland, Thailand, Yugoslavia, El-Salvador, Trinadad&Tobago, Peru, Hong, Holand-Netherlands |
| 14 | income_level | string | Predictor class if individual earns greater or less than $50000 per year. Values: <=50K, >50K |
Analiza braków danych
census_df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 48842 entries, 0 to 48841 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 age 48842 non-null int64 1 workclass 48842 non-null object 2 fnlwgt 48842 non-null float64 3 education 48842 non-null object 4 education_num 48842 non-null int64 5 marital_status 48842 non-null object 6 occupation 48842 non-null object 7 relationship 48842 non-null object 8 race 48842 non-null object 9 sex 48842 non-null object 10 capital_gain 48842 non-null float64 11 capital_loss 48842 non-null float64 12 hours_per_week 48842 non-null float64 13 native_country 48842 non-null object 14 income_level 48842 non-null object dtypes: float64(4), int64(2), object(9) memory usage: 5.6+ MB
W ramce danych nie wystąpują wartości None/Null. Z opisu ramki wiemy, że takowe braki, jeśli występują, są zakodowane odpowiednimi wartościami w kolumnach: ? dla danych nominalnych oraz -100000 dla numerycznych. Sprawdźmy, ile rekordów z takimi wartościami występują w ramce.
census_df[census_df == -100000].count()
age 0 workclass 0 fnlwgt 0 education 0 education_num 0 marital_status 0 occupation 0 relationship 0 race 0 sex 0 capital_gain 0 capital_loss 0 hours_per_week 0 native_country 0 income_level 0 dtype: int64
census_df[census_df == "?"].count()
age 0 workclass 2799 fnlwgt 0 education 0 education_num 0 marital_status 0 occupation 2809 relationship 0 race 0 sex 0 capital_gain 0 capital_loss 0 hours_per_week 0 native_country 857 income_level 0 dtype: int64
Okazuje się, że mamy braki w kolumnach workclass, occupation i workclass. Z czego dwie pierwsze kolumny w oczywisty sposób są powiązane, bo brak zawodu może automatycznie oznaczać brak klasy roboczej. Wartości "?" stanowią klasę zmiennej kategorycznej i nie ma sensu tego zmieniać w inny sposób.
Warto sprawdzić jaka jest średnia, odchylenie standardowe, wartość minimalna, maksymalna oraz kwartyle danych w poszczególnych kolumnach.
census_df.describe()
| age | fnlwgt | education_num | capital_gain | capital_loss | hours_per_week | |
|---|---|---|---|---|---|---|
| count | 48842.000000 | 4.884200e+04 | 48842.000000 | 48842.000000 | 48842.000000 | 48842.000000 |
| mean | 38.643585 | 1.896641e+05 | 10.078089 | 1079.067626 | 87.502314 | 40.422382 |
| std | 13.710510 | 1.056040e+05 | 2.570973 | 7452.019058 | 403.004552 | 12.391444 |
| min | 17.000000 | 1.228500e+04 | 1.000000 | 0.000000 | 0.000000 | 1.000000 |
| 25% | 28.000000 | 1.175505e+05 | 9.000000 | 0.000000 | 0.000000 | 40.000000 |
| 50% | 37.000000 | 1.781445e+05 | 10.000000 | 0.000000 | 0.000000 | 40.000000 |
| 75% | 48.000000 | 2.376420e+05 | 12.000000 | 0.000000 | 0.000000 | 45.000000 |
| max | 90.000000 | 1.490400e+06 | 16.000000 | 99999.000000 | 4356.000000 | 99.000000 |
Już dzięki temu zestawieniu możemy powiedzieć coś o danych. Można stwierdzić, że dużo osób pracuje 40 godzin tygodniowo. Porównując średnie z wartościami minimalnymi i maskymalnymi dla kolumny capital_gain i capital_loss można stwierdzić, że wartości są bardzo rozchwiane i występują obserwacje odstające. Przyjrzyjmy sie histogramom.
Rozkłady zmiennych
census_df.hist(figsize=(15, 20), bins=40)
plt.show()
Wspomniane wcześniej wnioski się potwirerdzają. Najwięcej osób pracuje 40 godzin tygodniowo, choć znajdują się osobu pracujące powyżej 80. Kolumny capital_gain i capital_loss wyglądają niekorzytsnie, gdyż mają przeważającą liczbę obserwacji zerowych. Wiek ma dosyć regularny rozkład, ale bardziej skośny prawostronnie, czyli w stronę osób młodszych. Wydaje się też, że dane zostały ucięte na wieku 90 lat. Jak wynika z opisu, kolumna fnlwgt jest parametrem, który ma oddawać wagę danego rekordu. Oznacza to więc, że nie będzie miała znaczenia dla oceny zarobków i można ją usunąć. Z kolei zmienna education_num wydaje się być zmienną kategoryczną.
census_df.drop("fnlwgt", axis=1, inplace=True)
census_df[['education', 'education_num', 'age']].groupby(['education', 'education_num']).size().reset_index(name='counts').sort_values(by = "education_num")
| education | education_num | counts | |
|---|---|---|---|
| 13 | Preschool | 1 | 83 |
| 3 | 1st-4th | 2 | 247 |
| 4 | 5th-6th | 3 | 509 |
| 5 | 7th-8th | 4 | 955 |
| 6 | 9th | 5 | 756 |
| 0 | 10th | 6 | 1389 |
| 1 | 11th | 7 | 1812 |
| 2 | 12th | 8 | 657 |
| 11 | HS-grad | 9 | 15784 |
| 15 | Some-college | 10 | 10878 |
| 8 | Assoc-voc | 11 | 2061 |
| 7 | Assoc-acdm | 12 | 1601 |
| 9 | Bachelors | 13 | 8025 |
| 12 | Masters | 14 | 2657 |
| 14 | Prof-school | 15 | 834 |
| 10 | Doctorate | 16 | 594 |
Z zsumowania rekordów zawierających unikalne pary education + education_num wynika, że ta druga kolumna jest tylko numerycznym zakodowaniem kolumny opisującej osiągnięty etap edukacji z zachowaniem porządku, zatem kolumna education nie będzie niezbędna w dalszej predykcji, dlatego możemy ją usunąć.
census_df.drop("education", axis=1, inplace=True)
Zamiana wartości ostatniej kolumny:
ponieważ łatwiej pracuje się ze zmiennymi numerycznymi
census_df['income_level'] = census_df['income_level'].replace(['<=50K','>50K'],[0, 1])
fig_corr = px.imshow(census_df.corr(), color_continuous_scale='RdBu_r', range_color=(-0.5,0.5))
fig_corr.show()
Interesuje nas jak zmienne korelują ze zmienną income_level, ponieważ właśnie ją będziemy przewidywać. Widać, że wszystkie korelują z nią dodatnio.
census_df['income_level'].value_counts()
0 37155 1 11687 Name: income_level, dtype: int64
Dodatkowo warto wiedzieć, że znacznie mniej osób zarabia powyżej progu 50K$. Teraz przyjrzyjmy sie rozkładom zmiennych typowo ciągłych z uwzględnieniem poziomu zarobków.
Wiek
sns.violinplot(y = census_df['age'], x = census_df['income_level'], palette=["red", "green"], scale = "count")
plt.show()
Z rozkładu zmiennej age z uwzględnieniem podziału na poziom zarobków, można stwierdzić, że wśród osobów zarabiających lepiej, rozkład przesuwa się w stronę osób starszych.
Podzielmy tę zmienną na kategorie, oznaczając każdą kategorię jako koniec danego przedziału wieku, czyli:
tmp = pd.cut(census_df['age'], bins=[16, 30, 40, 50, 60, 70, 80, 100], labels=[30, 40, 50, 60, 70, 80, 90])
tmp[tmp.isna()] #sprawdzenie czy w ten sposób nie powstaną jakieś braki danych
Series([], Name: age, dtype: category Categories (7, int64): [30 < 40 < 50 < 60 < 70 < 80 < 90])
census_df['age'] = pd.cut(census_df['age'], bins=[16, 30, 40, 50, 60, 70, 80, 100], labels=[30, 40, 50, 60, 70, 80, 90])
Przepracowane godziny w tygodniu
sns.violinplot(y = census_df['hours_per_week'], x = census_df['income_level'], palette = ['red', 'green'], scale = "count")
<AxesSubplot:xlabel='income_level', ylabel='hours_per_week'>
Dzięki zachowaniu skali ilości na wykresach można zauważyć tendecję, że osoby które pracują więcej, zarabiają lepiej. Świadczy o tym skupienie prawie wszystkich obserwacji powyżej poziomu 35h dla poziomu 1, podczas, gdy dla poziomu 0 widać duży peak w okolicach 40, ale dużą liczbę obserwacji poniżej tej wartości. Tę zmienną również możemy zmienić na kategoryczną, dzieląc co 10.
tmp = pd.cut(census_df['hours_per_week'], bins=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], labels=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
tmp[tmp.isna()] #sprawdzenie czy w ten sposób nie powstaną jakieś braki danych
Series([], Name: hours_per_week, dtype: category Categories (10, int64): [10 < 20 < 30 < 40 ... 70 < 80 < 90 < 100])
census_df['hours_per_week'] = pd.cut(census_df['hours_per_week'], bins=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], labels=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
Jako, że zmienne capital_gain i capital_loss także są typowo ciągłe, żeby uwzględnić ich rozkłady pozbędziemy się najpierw obserwacji, dla których wartości powyższych kolumn są zerowe, ponieważ wartości tych jest bardzo dużo.
df = census_df[census_df['capital_gain'] > 0]
sns.violinplot(y = df['capital_gain'], x = df['income_level'], palette = ['red', 'green'], scale = "count")
plt.show()
Zobaczmy jeszcze jak to dokładniej wygląda na histogramach:
tmp = census_df.loc[(census_df['capital_gain']>0) & (census_df['income_level']==1)]
tmp2 = census_df.loc[(census_df['capital_gain']>0) & (census_df['income_level']==0)]
fig, axes = plt.subplots(1, 2)
tmp['capital_gain'].hist(bins = 40, figsize = (12,6), ax=axes[0], color = 'green')
tmp2['capital_gain'].hist(bins = 40, figsize = (12,6), ax=axes[1], color = 'red')
plt.ylim((0,1000))
plt.show()
Widać, że w przypadku zmiennej capital_gain więcej osób, które mają przychód >50 000 \$ osiąga zysk na poziomie 0-20 000\\$. Wydaje się też, że dane zostały ucięte dla wartości powyżej 100 000\$. Dla osób w grupie o niższych przychodach widać, że wartości zysku nie przekraczają wartości 50 000\\$, ponieważ to wlicza się do ogólnego przychodu. Aby to było możliwe ktoś musiałby usyskać dużą staratę kapitałową, ponieważ przychód liczony jest jako capital_gain - capital_loss + salary
Tę kolumnę również zmienimy na zmienną kategoryczną.
census_df['capital_gain'].max()
99999.0
tmp = pd.cut(census_df['capital_gain'], bins=[-1, 1, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, np.inf],
labels=[0, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000])
tmp[tmp.isna()] #sprawdzenie czy w ten sposób nie powstaną jakieś braki danych
Series([], Name: capital_gain, dtype: category Categories (11, int64): [0 < 10000 < 20000 < 30000 ... 70000 < 80000 < 90000 < 100000])
census_df['capital_gain'] = pd.cut(census_df['capital_gain'], bins=[-1, 1, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, np.inf],
labels=[0, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000])
census_df.head(10)
| age | workclass | education_num | marital_status | occupation | relationship | race | sex | capital_gain | capital_loss | hours_per_week | native_country | income_level | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 40 | State-gov | 13 | Never-married | Adm-clerical | Not-in-family | White | Male | 10000 | 0.0 | 40 | United-States | 0 |
| 1 | 50 | Self-emp-not-inc | 13 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 0 | 0.0 | 20 | United-States | 0 |
| 2 | 40 | Private | 9 | Divorced | Handlers-cleaners | Not-in-family | White | Male | 0 | 0.0 | 40 | United-States | 0 |
| 3 | 60 | Private | 7 | Married-civ-spouse | Handlers-cleaners | Husband | Black | Male | 0 | 0.0 | 40 | United-States | 0 |
| 4 | 30 | Private | 13 | Married-civ-spouse | Prof-specialty | Wife | Black | Female | 0 | 0.0 | 40 | Cuba | 0 |
| 5 | 40 | Private | 14 | Married-civ-spouse | Exec-managerial | Wife | White | Female | 0 | 0.0 | 40 | United-States | 0 |
| 6 | 50 | Private | 5 | Married-spouse-absent | Other-service | Not-in-family | Black | Female | 0 | 0.0 | 20 | Jamaica | 0 |
| 7 | 60 | Self-emp-not-inc | 9 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 0 | 0.0 | 50 | United-States | 1 |
| 8 | 40 | Private | 14 | Never-married | Prof-specialty | Not-in-family | White | Female | 20000 | 0.0 | 50 | United-States | 1 |
| 9 | 50 | Private | 13 | Married-civ-spouse | Exec-managerial | Husband | White | Male | 10000 | 0.0 | 40 | United-States | 1 |
df = census_df[census_df['capital_loss'] > 0]
sns.violinplot(y = df['capital_loss'], x = df['income_level'], palette = ['red', 'green'], scale = "count")
plt.show()
Zobaczmy jeszcze jak to dokładniej wygląda na histogramach:
tmp = census_df.loc[(census_df['capital_loss']>0) & (census_df['income_level']==1)]
tmp2 = census_df.loc[(census_df['capital_loss']>0) & (census_df['income_level']==0)]
fig, axes = plt.subplots(1, 2)
tmp['capital_loss'].hist(bins = 12, figsize = (12,6), ax=axes[0], color = 'green')
tmp2['capital_loss'].hist(bins = 12, figsize = (12,6), ax=axes[1], color = 'red')
plt.ylim((0,625))
plt.show()
capital_loss w przypadku obu grup osób nie przekracza 5000\$ i najwięcej osób (oprócz tych dla których odnotowano zerową wartość tej zmiennej) osiąga straty kapitałowe w okolicy 2000\\$. Rozkłady są bardzo podobne, jednak dla osób, które odnotowały mniejszy dochód bardziej przesunięte w stronę mniejszych wartości, a dla drugiej grupy w stronę większych wartości.
Tę zmienną również przekształcimy na kategoryczną.
census_df['capital_loss'].max()
4356.0
tmp = pd.cut(census_df['capital_loss'], bins=[-1, 1, 500, 1500, 2000, 2500, 3000, 3500, 4000, np.inf],
labels=[0, 500, 1500, 2000, 2500, 3000, 3500, 4000, 4500])
tmp[tmp.isna()] #sprawdzenie czy w ten sposób nie powstaną jakieś braki danych
Series([], Name: capital_loss, dtype: category Categories (9, int64): [0 < 500 < 1500 < 2000 ... 3000 < 3500 < 4000 < 4500])
census_df['capital_loss'] = pd.cut(census_df['capital_loss'], bins=[-1, 1, 500, 1500, 2000, 2500, 3000, 3500, 4000, np.inf],
labels=[0, 500, 1500, 2000, 2500, 3000, 3500, 4000, 4500])
Teraz przyjrzymy się zależnościom między zmiennymi kategorycznymi i zmienną poziomującą zarobki
Kraj pochodzenia
plt.figure(figsize=(20, 10))
plt.xticks(rotation = 90)
df = census_df.groupby(['native_country']).size().reset_index(name='counts')
plt.bar(x="native_country", height="counts", data=df);
plt.show()
Jako że przeważająca liczba osób ze zbioru pochodzi ze Stanów Zjednoczonych, dla efektywniejszego wyciągania wniosków lepiej będzie zbadać procentowy udział osób zarabiających na danym poziomie dla każdego z krajów, z którego pochodzą emigranci.
df = census_df.groupby('native_country')["income_level"].value_counts(normalize = True).unstack('income_level').sort_values(by = 1, ascending = False)
df.plot.barh(stacked=True, figsize = (18, 18), color = ['#ff5b5c', 'green'])
plt.show()
Widać, że wśród krajów, z których najmniejszy procent zarabia powyżej 50K to kraje Ameryki Środkowej. Po drugiej stronie przeważają z kolei kraje Europy i Azji. Można stwierdzić, że zależy to od rozwoju danych krajów. Jasne wydaje się, że dla osób pochodzących z tych bardziej rozwiniętych łatwiej jest o wyższe zarobki.
W celu zredukowania klas można by pogupować te dane w zależności od uzyskowanego dochodu, szczególnie że emigrantów jest stosunkowo mało, więc raczej nie wpłynęłoby to znacząco na dokładność predykcji, a mogłoby przyspieszyć ten proces. Spróbujmy więc zastosować taką redukcję:
census_df.groupby(['native_country']).size().reset_index(name='counts').sort_values(by = 'counts', ascending=False).head(10)
| native_country | counts | |
|---|---|---|
| 39 | United-States | 43832 |
| 26 | Mexico | 951 |
| 0 | ? | 857 |
| 30 | Philippines | 295 |
| 11 | Germany | 206 |
| 33 | Puerto-Rico | 184 |
| 2 | Canada | 182 |
| 8 | El-Salvador | 155 |
| 19 | India | 151 |
| 5 | Cuba | 138 |
Widać, że jedyne bardziej wyróżniające się jednostki to United-States oraz Mexico. W takim razie wszystkie inne (w tym braki danych) możemy uznać jako Others
census_df.loc[(census_df.native_country != 'United-States') & (census_df.native_country != 'United-States'), 'native_country'] = "Other"
Wykształcenie
plt.figure(figsize=(15,8))
df = census_df.groupby(['education_num', 'income_level']).size().reset_index(name='counts')
sns.barplot(x="education_num", y="counts", hue="income_level", data=df, ci=None, palette=["red", "green"]);
plt.show()
Możemy zaobserwować, że najwięcej osób z naszej próbki ma wykształcenie średnie (9 - HS-grad) lub uczęszczali na studia ale nie zdobyli stopnia naukowego (10 - some college). Dużo jest także osób ze stopniem licencjackim (13 - Bachelor). Widać wyrażnie, że im wyższy poziom w naukowej drabince, tym osób zarabiających powyżej 50K jest coraz więcej w stosunku do tych zarabiających mniej. Dla stopni powyżej magistra (od 14 - Masters) osoby oznaczone numerem 1 przeważają.
Stan cywilny
plt.figure(figsize=(12,8))
df = census_df.groupby(['marital_status', 'income_level']).size().reset_index(name='counts')
sns.barplot(x="marital_status", y="counts", hue="income_level", data=df, ci=None, palette=["red", "green"]);
plt.xticks(rotation = 30)
plt.show()
Największe grupy osób są w związku z małżeńskim z osobą cwyilną, ewentualnie nigdy nie zawierały takiego związku lub są rozwiedzione. Pozostałe grupy są wyraźnie mniej liczne, a najmniejszą stanowią osoby w związku małzeńskim z osobą wojskową. W każdej grupie jest przewaga osób zarabiających mniej niż 50K. Jednak grupa małzeństw z osobami cywilnymi wyróżnia się najbardziej wyrównaną proporcją osób o różnym poziomie zarobków, podczas gdy wśród pozostałych zdecydowanie więcej osób nie zarabia więcej niz 50K. Może to nasuwać wniosek, że osoby w związku małżeńskim mają większą szansę na stabilność finansową, a co za tym idzie, lepsze zarobki w ogólności.
plt.figure(figsize=(12,8))
df = census_df.groupby(['relationship', 'income_level']).size().reset_index(name='counts')
sns.barplot(x="relationship", y="counts", hue="income_level", data=df, ci=None, palette=["red", "green"]);
plt.show()
Dosyć podobną do poprzedniej zmiennej jest zmienna relationship. Wnioski się nakładają, bo ponownie widać, że proporcja osób o różnych poziomach zarobków jest wyrównana dla osób w związku małżeńskim. Dla pozostałych grup zdecydowanie przeważają osoby o mniejszych zarobkach.
Klasa robocza i zawód
plt.figure(figsize=(12,8))
df = census_df[census_df.workclass != "?"].groupby(['workclass', 'income_level']).size().reset_index(name='counts')
sns.barplot(x="workclass", y="counts", hue="income_level", data=df, ci=None, palette=["red", "green"]);
plt.show()
Przewazająca część osób z próbki to osoby pracujące w sektorze prywatnym. Mimo, że jest tam również najwięcej osób, które zarabiają powyżej 50K, to proporcjonalnie zdecydowanie więcej osób zarabia więcej w sektorze samozatrudniających się.
Ze względu na duże zróznicowanie zawodów, najpierw przyjrzymy się jak dużo osób pracuje w poszczególnych dziedzinach.
plt.figure(figsize=(20, 5))
plt.xticks(rotation = 45)
df = census_df[census_df.occupation != "?"].groupby(['occupation']).size().reset_index(name='counts').sort_values(by = "counts")
plt.bar(x="occupation", height="counts", data=df);
plt.show()
Widać, że wśród osób z naszej ramki rozkład zawodów jest bardziej równowmierny. A teraz sprawdzimy jak duża część osób zarabai więcej niż 50K dla każdej grupy.
df = census_df[census_df.occupation != "?"].groupby('occupation')["income_level"].value_counts(normalize = True).unstack('income_level').sort_values(by = 1, ascending = False)
df.plot.barh(stacked=True, figsize = (18, 10), color = ['#ff5b5c', 'green'])
plt.show()
Dwie grupy o największym stosunku oznaczonych 1 do oznaczonych 0 to osoby o stanowiskach kierowniczych i osoby pracujące w specjalizacji. Są to także ogólnie najliczniejsze grupy pracujących, więc można uznać że praca w tych dwóch obszarach daje lepsze wynagrodzenie Nie można tego powiedzieć o osobach pracujących w zawodach odpowiedzialnych za sprzątanie i zajmowanie się domem, gdzie osób zarabaijących powyżej 50K jest bardzo mało.
Płeć i rasa
df1 = census_df[census_df.income_level == 0]
df1 = df1.groupby(['sex', 'race']).size().reset_index(name='counts')
df1 = pd.pivot(df1, index = 'race', columns = 'sex', values = 'counts')
df2 = census_df[census_df.income_level == 1]
df2 = df2.groupby(['sex', 'race']).size().reset_index(name='counts')
df2 = pd.pivot(df2, index = 'race', columns = 'sex', values = 'counts')
fig, (ax1, ax2) = plt.subplots(1,2)
sns.heatmap(df1, annot=True, cmap='Reds', ax=ax1, fmt='g', norm=LogNorm())
sns.heatmap(df2, annot=True, cmap='Greens', ax=ax2, fmt='g', norm=LogNorm())
fig.set_size_inches(18.5, 6)
plt.show()
Dla lepszego zobrazowania ilości osób w poszczególnych grupach, używamy skali logarytmicznej. Wśród naszych obserwacji występuje więcej osób płci męskiej, a także przeważają osoby białe.
tmp2 = census_df
tmp2['sex'] = tmp2['sex'].replace(['Famale','Male'],[1, 0])
tmp2 = tmp2[tmp2.race != "?"].groupby('income_level')["sex"].value_counts(normalize = True).unstack('sex')
tmp2.plot.barh(stacked=True, figsize = (15, 5), color = ['lightblue', 'violet'])
plt.show()
Można zauważyć, że w przypadku osób zarabiających poniżej 50K, stosunek kobiet do mężczyzn jest znacznie większy. Oznacza to, że kobiety częściej zarabiają mniejsze stawki. Dysproporcje można też dostrzec porównując grupy rasowe:
tmp = census_df
tmp = tmp[tmp.race != "?"].groupby('race')["income_level"].value_counts(normalize = True).unstack('income_level')
tmp.plot.barh(stacked=True, figsize = (15, 5), color = ['#ff5b5c', 'green'])
plt.show()
Można zauważyć, że wśród osób Czarnoskórych oraz rdzennych Amerykanów i Inuitów występuje najmniejszy procent osób zarabiających powyżej 50K. Z kolei ludzie z Azji i wysp Pacyfiku oraz ludzie biali mają mniej drastyczną różnicę procentową i około 30% zarabia lepiej.
Dzięki eksploracji danych pod kątem dalszej predykcyjności zmiennej określającej, czy ktoś zarabia mniej czy więcej niż 50 000$ rocznie doszliśmy do następujących wniosków:
fnlwgt nie wnosi żadnej wartości pod tym kątem, dlatego została usuniętaeducation i education_num określają dokładnie te same informacje, tylko przekazane w inny sposób. Kolumna, której będziemy używać to education_num, ponieważ z danymi numerycznymi, które określają hierarię zmiennej łatwiej się pracuje. Druga została usunięta, ponieważ nie będzie używana w predykcji. Mamy jednak w pamięci oznaczania, których dostarcza.
Zmienna ta pokazuje, że im wyższy poziom edukacji, tym osób zarabiających powyżej 50K jest coraz więcej w stosunku do tych zarabiających mniej. age ujawnia tendencję, że osoby o wyższym przychodzie są starsze, najwięcej w okolicach 45 lat, natomiast wśród drugiej grupy dominują osoby mlode, około 20-letnie. Zmienna ta została podzielona na zmienną kategoryczną o 7 kategoriachhours_per_week wynika, że osoby które pracują więcej, zarabiają lepiej, ponieważ znaczna większość osob o wyższych dochodach pracuje więcej niż 35 godin tygodniowo. Ta zmienna również została zamieniona na kategorycznącapital_gain wnosi informację, że wsród osób, które osiągają zysk kapitałowy na poziomie 10000\$-20000\\$` jest znacznie więcej osób dochodzie większym niż 50000\$, natpiast osoby o dochodzie rocznym mniejszym od tej wartości nie przekraczają zysku kapitałowego o wartości 50000\\$. Zmienna ta została przekształcona na kategoryczną. capital_loss wydaje się, że niezbyt wpływa na dochód roczny, ponieważ rozkłady dla obu grup osób są zbliżone. Zmienna ta została przekształcona na kategoryczną. native_country dostarcza informacji, że więkość osób stanowią osoby pochodzące ze Stanów Zjednoczonych. Jedyną wybijającą się grupą poza nimi są obywatele Meksyku. Pozostali stanowią niewielką część, dlatego zostali zgrupowani do kategorii Others. Widać było jednak wsród nich zależność, że osoby z bardziej rozwiniętych państw zarabiają więcej.marital_status wskazuje, że dla każdej z podgrup osób zarabiających mniej jest więcej, jednak różnica ta jest najmniejsza w przypadku osób w związkach małżeńskichrelationship wynikają podobne informacje jak z marital_status. Tu również proporcja osób o różnych poziomach zarobków jest wyrównana dla osób w związku małżeńskim, a dla innych grup występuje znacząca przewaga osób o mniejszych dochodach. W przyszłości można sprawdzić, czy predykcyjność mocno się zmieni po usunięci jednej z nich oraz sprawdzić którą lepiej usunąć.workclass dowiadujemy się, że jedyną grupą, dla której przeważają osoby o wyższych dochodach to grupa osób, które posiadają własną firmęoccupation dostarcza informacji, że największy współczynnik osób zarabiającyh lepiej do tych zarabiajaącyh gorzej odnotowany jest wśród osób na stanowiskach kierowniczych i pracujących w specjalizacji. Najmniejszy jest natomiast dla osób pracujących w zawodach odpowiedzialnych za sprzątanie i zajmowanie się domem, gdzie osób zarabaijących powyżej 50K jest bardzo mało.sex i race można odczytać, że kobiety rzadziej zarabiają powyżej 50 000\$ Jeśli chodzi o rasy to większy odsetek lepiej zarabijących jest wśród ludności białej oraz z Azji i wysp Pacyfiku.